
package ubo.master1.jeupoker.ia;

import ubo.master1.jeupoker.gui.LaCart;

/**
 * Evaluer la valeur d'une main.
 *
 */
public class EvaluerUneMain {
    
    /** Nombre de classement des mains. */
    private static final int NO_OF_RANKINGS  = 6;
    
    /** Le nombre maximum de paire. */
    private static final int MAX_NO_OF_PAIRS = 2;
    
    /** The ranking factors (powers of 13, the number of ranks). */
    private static final int[] RANKING_FACTORS = {371293, 28561, 2197, 169, 13, 1};
    
    /** les type de combinaison des cartes. */
    private CombinaisonCarte type;
    
    /** La valeur de la main. */
    private int value = 0;
    
    /** Les cartes. */
    private final LaCart[] cards;
    
    /** Les carte numéroté. */
    private int[] rankDist = new int[LaCart.NO_OF_RANKS];
    
    /** Les couleurs des cartes d'une main. */
    private int[] suitDist = new int[LaCart.NO_OF_SUITS];
    
    /** nombre de paire. */
    private int noOfPairs = 0;
    
    /** Les paires des cartes numérotés. */
    private int[] pairs = new int[MAX_NO_OF_PAIRS];
    
    /** noméroation d'un flush (carte de même couleurs) */
    private int flushSuit = -1;
    
    /** Les numero de carte d'un flush. */
    private int flushRank = -1;
    
    /** numérotation d'un straight (suite successive de numéro de carte). */
    private int straightRank = -1;
    
    /** Si les straight termine avec un as. */
    private boolean wheelingAce = false;
    
    /** numérotation de triplet (trois cartes de meme numéro Brelan). */
    private int tripleRank = -1;
    
    /** numérotation de quatre cartes de même numéro (Carré). */
    private int quadRank = -1;
    
    /** Le poid (valeur) d'une main (par ordre decroissant). */
    private int[] rankings = new int[NO_OF_RANKINGS];

    /**
     * Constructeur.
     *
     * @param  hand  La main à evaluer.
     */
    public EvaluerUneMain(UneMain hand) {
        cards = hand.getCards();
        
        // Find patterns.
        calculateDistributions();
        findStraight();
        findFlush();
        findDuplicates();
        
        // Trouver si main fait parti des cas particulier.
        boolean isSpecialValue =
                (isStraightFlush() ||
                 isFourOfAKind()   ||
                 isFullHouse()     ||
                 isFlush()         ||
                 isStraight()      ||
                 isThreeOfAKind()  ||
                 isTwoPairs()      ||
                 isOnePair());
        if (!isSpecialValue) {
            calculateHighCard();
        }
        
        // Calculer la valeur de la main.
        for (int i = 0; i < NO_OF_RANKINGS; i++) {
            value += rankings[i] * RANKING_FACTORS[i];
        }
    }
    
    /**
     * Retourner le type de la main.
     *
     * @return  Type de la main
     */
    public CombinaisonCarte getType() {
        return type;
    }
    
    /**
     * Retourner la valeur entiére d'une mains.
     * 
     * Cette methode est utilisé pour comparer les mains.
     *
     * @return  La valeur de la main
     */
    public int getValue() {
        return value;
    }
    
    /**
     * Calculer les cartes coloré et numéroté..
     */
    private void calculateDistributions() {
        for (LaCart card : cards) {
            rankDist[card.getNumeroCarte()]++;
            suitDist[card.getCouleurCarte()]++;
        }
    }
    
    /**
     * Vérifier si c'est un flush (carte de même couleur).
     */
    private void findFlush() {
        for (int i = 0; i < LaCart.NO_OF_SUITS; i++) {
            if (suitDist[i] >= 5) {
                flushSuit = i;
                for (LaCart card : cards) {
                    if (card.getCouleurCarte() == flushSuit) {
                        if (!wheelingAce || card.getNumeroCarte() != LaCart.ACE) {
                            flushRank = card.getNumeroCarte();
                            break;
                        }
                    }
                }
                break;
            }
        }
    }

    /**
     * Chercher si c'est un Straight,(cinq carte ordonné).
     * 
     */
    private void findStraight() {
        boolean inStraight = false;
        int rank = -1;
        int count = 0;
        for (int i = LaCart.NO_OF_RANKS - 1; i >= 0 ; i--) {
            if (rankDist[i] == 0) {
                inStraight = false;
                count = 0;
            } else {
                if (!inStraight) {
                    // First card of the potential Straight.
                    inStraight = true;
                    rank = i;
                }
                count++;
                if (count >= 5) {
                    // Found a Straight!
                    straightRank = rank;
                    break;
                }
            }
        }
        // Cas cinq cartes de même couleur et l'ensemble contien un As.
        if ((count == 4) && (rank == LaCart.FIVE) && (rankDist[LaCart.ACE] > 0)) {
            wheelingAce = true;
            straightRank = rank;
        }
    }

    /**
     * Chercher s'il y a des main dupliqué (de même poid).
     */
    private void findDuplicates() {
         for (int i = LaCart.NO_OF_RANKS - 1; i >= 0 ; i--) {
            if (rankDist[i] == 4) {
                quadRank = i;
            } else if (rankDist[i] == 3) {
                tripleRank = i;
            } else if (rankDist[i] == 2) {
                if (noOfPairs < MAX_NO_OF_PAIRS) {
                    pairs[noOfPairs++] = i;
                }
            }
        }
    }

    /**
     * Calculer la main haute..
     */
    private void calculateHighCard() {
        type = CombinaisonCarte.HIGH_CARD;
        rankings[0] = type.getValue();
        // trouver les cinq plus grand numéro.
        int index = 1;
        for (LaCart card : cards) {
            rankings[index++] = card.getNumeroCarte();
            if (index > 5) {
                break;
            }
        }
    }

    /**
     * Retourner True si la main contien une paire.
     * 
     * @return True si la mains contien une seul paire.
     */
    private boolean isOnePair() {
        if (noOfPairs == 1) {
            type = CombinaisonCarte.ONE_PAIR;
            rankings[0] = type.getValue();
            // Trouver le numéro des paires.
            int pairRank = pairs[0];
            rankings[1] = pairRank;
            int index = 2;
            for (LaCart card : cards) {
                int rank = card.getNumeroCarte();
                if (rank != pairRank) {
                    rankings[index++] = rank;
                    if (index > 4) {
                        break;
                    }
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Retourner true si la main contien deux paires.
     * 
     * principalement le rong est calculé par le paire le plus haut puis le plus petit
     * et la care restante
     *
     * @return True si la main contien deux paires.
     */
    private boolean isTwoPairs() {
        if (noOfPairs == 2) {
            type = CombinaisonCarte.TWO_PAIRS;
            rankings[0] = type.getValue();
            // Trouver le rong le plus grand et le plus petit
            int highRank = pairs[0];
            int lowRank  = pairs[1];
            rankings[1] = highRank;
            rankings[2] = lowRank;
            // Trouver le rong de la carte restante.
            for (LaCart card : cards) {
                int rank = card.getNumeroCarte();
                if ((rank != highRank) && (rank != lowRank)) {
                    rankings[3] = rank;
                    break;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Retourner true si combinaison d'un triplet (Trois cartes de meme numéro)
     * 
     * Le rong du triplet est calculé par valeur des carte composant le triplet.
     * les deux cartes restante sont utilisé.
     *
     * @return True Si la main contient une combinaison d'un triplet.
     */
    private boolean isThreeOfAKind() {
        if (tripleRank != -1) {
            type = CombinaisonCarte.THREE_OF_A_KIND;
            rankings[0] = type.getValue();
            rankings[1] = tripleRank;
            // rong des deux cartes restantes.
            int index = 2;
            for (LaCart card : cards) {
                int rank = card.getNumeroCarte();
                if (rank != tripleRank) {
                    rankings[index++] = rank;
                    if (index > 3) {
                       break;
                    }
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returner true si les cartes compose un straight (carte numéroté par ordre croisant).
     * 
     * La valeur (poid) du straignt est caclulé par la valeur de la carte la plus 
     * élevé de la main.
     *
     * @return True si les cartes compose un straight.
     */
    private boolean isStraight() {
        if (straightRank != -1) {
            type = CombinaisonCarte.STRAIGHT;
            rankings[0] = type.getValue();
            rankings[1] = straightRank;
            return true;
        } else {
            return false;
        }
    }

    /**
     * Retourner true si la main contien un flush (cinq cartes de même couleur).
     * 
     * Le poid du flush est calculé par le rong de la carte la plus élévé du flush.
     * 
     * @return True si la main contien un flush.
     */
    private boolean isFlush() {
        if (flushSuit != -1) {
            type = CombinaisonCarte.FLUSH;
            rankings[0] = type.getValue();
            int index = 1;
            for (LaCart card : cards) {
                if (card.getCouleurCarte() == flushSuit) {
                    int rank = card.getNumeroCarte();
                    if (index == 1) {
                        flushRank = rank;
                    }
                    rankings[index++] = rank;
                    if (index > 5) {
                        break;
                    }
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Retourner true si la main contient un full house (Full) un triplet et un double .
     * 
     * Le poid du full house est basé sur le rong du triplet 
     * puis sur le rong du paire
     *
     * @return True Retourner true si la main contient.
     */
    private boolean isFullHouse() {
        if ((tripleRank != -1) && (noOfPairs > 0)) {
            type = CombinaisonCarte.FULL_HOUSE;
            rankings[0] = type.getValue();
            rankings[1] = tripleRank;
            rankings[2] = pairs[0];
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Returns true si la main contient un  Four of a Kind (Carré ) quatre carte de même numéroation.
     * 
     * La valeur (poid) du carré est calculé par l'ordre du numéro de carte composant le carré 
     * puis par la carte restante.
     *
     * @return True si la main contient un carré.
     */
    private boolean isFourOfAKind() {
        if (quadRank != -1) {
            type = CombinaisonCarte.FOUR_OF_A_KIND;
            rankings[0] = type.getValue();
            rankings[1] = quadRank;
            // Get the remaining card as kicker.
            int index = 2;
            for (LaCart card : cards) {
                int rank = card.getNumeroCarte();
                if (rank != quadRank) {
                    rankings[index++] = rank;
                    break;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Retourner true si les cartes compose un Straight Flush (carte de même couleurs et ordondés ).
     * 
     * Si l'Ace est le numéro de carte le plus élévé alors on à un royal flush
     * 
     * Le poid(rong) du Straight flush est calculé sur la position de la carte du 
     * Straight la plus élévé.
     * 
     * @return True Si les cartes compose un Straight Flush.
     */
    private boolean isStraightFlush() {
        if (straightRank != -1 && flushRank == straightRank) {
            // Vérifier si c'est un Straight flush
            int straightRank2 = -1;
            int lastSuit = -1;
            int lastRank = -1;
            int inStraight = 1;
            int inFlush = 1;
            for (LaCart card : cards) {
                int rank = card.getNumeroCarte();
                int suit = card.getCouleurCarte();
                if (lastRank != -1) {
                    int rankDiff = lastRank - rank;
                    if (rankDiff == 1) {
                        // Numéro de carte consécutive straight possible.
                        inStraight++;
                        if (straightRank2 == -1) {
                            straightRank2 = lastRank;
                        }
                        if (suit == lastSuit) {
                            inFlush++;
                        } else {
                            inFlush = 1;
                        }
                        if (inStraight >= 5 && inFlush >= 5) {
                            //Straight
                            break;
                        }
                    } else if (rankDiff == 0) {
                    } else {
                        straightRank2 = -1;
                        inStraight = 1;
                        inFlush = 1;
                    }
                }
                lastRank = rank;
                lastSuit = suit;
            }
            
            if (inStraight >= 5 && inFlush >= 5) {
                if (straightRank == LaCart.ACE) {
                    //  Flush Royal.
                    type = CombinaisonCarte.ROYAL_FLUSH;
                    rankings[0] = type.getValue();
                    return true;
                } else {
                    // Straight Flush.
                    type = CombinaisonCarte.STRAIGHT_FLUSH;
                    rankings[0] = type.getValue();
                    rankings[1] = straightRank2;
                    return true;
                }
            } else if (wheelingAce && inStraight >= 4 && inFlush >= 4) {
                type = CombinaisonCarte.STRAIGHT_FLUSH;
                rankings[0] = type.getValue();
                rankings[1] = straightRank2;
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
    
}
